This page last changed on May 21, 2012 by brian.

Extracting the Fiducial Spots from Images

Assumptions

The technique used here assumes that the fiducial spots on the image are very bright compared to the rest of the image

Reading the Image

import java.io.File
import java.net.URL
import org.mbari.esp.ia.services.ToolBox
// READ ESP TIFF IMAGE
val io = ToolBox.imageIOService
val image = io.read(new URL("file:/test08/pcr11jun2520h2000ml40s.tif")).getProcessor
// WRITE AS PNG 
io.write(image, new File("pcr11jun2520h2000ml40s.png"))

Extracting Bright Spots

The fiducials are typically the brightest areas on the image, especially in contrast to pixels immediately adjacent to the fiducial spots. We take advantage of this quality and isolate the fiducials by calculating the standard deviation of a 3x3 kernel across the entire image. The reason is clearly evident when looking at a surface plot of the raw image vs. the standard deviation. On the raw image, the fiducials may not be distinct from noisy backgrounds. However, when when using the standard deviation the fiducials sharply contrast with the rest of the image. In the images below, the 3 brightest peaks are the fiducial spots on the image.


We can extract the bright regions with the following code:

import org.mbari.esp.ia.imglib.BrightSpotExtractorFn
val regions = BrightSpotExtractorFn(rawImage)

The internals of this function use a very standard pattern for spot extraction. The steps boil down to:

  1. Convert the image to 8-bit Gray so that we can calculate the histogram very quickly.
  2. Threshold the image using maximum entropy. Renyi's entropy also works well but takes longer to calculate.
  3. Convert the binary threhold image to 16-bit before flood filling. Sometimes there is more than 256 regions, which can not be represented in an 8-bit image.
  4. Remove the area outside of a central circle. In general, we use a circle radius of 75% of the minimum radius of an image.
  5. Flood fill the image. Regions with less than 5 pixels are considered to be noise and are ignored.

Here's the function in it's entirety:

package org.mbari.esp.ia.imglib

import ij.process.ImageProcessor
import org.mbari.esp.ia.awt.ShortRegion

object BrightSpotExtractorFn extends (ImageProcessor => Iterable[ShortRegion]) {

    def apply(from: ImageProcessor): Iterable[ShortRegion] = {
        // Calculate local standard deviation for every 3x3 block of pixels
        val to = StdFn(from)

        val threshold = To8BitGrayFn andThen MaximumEntropyThresholdFn
        val floodFill = {
            val floodFillWMinFn = FloodFillWithMinimumFn(5, _: ImageProcessor)
            val removeFilterFn = RemoveNonFilterAreaFn(0.75, _: ImageProcessor)
            To16BitGrayFn andThen removeFilterFn andThen floodFillWMinFn
        }
        val fill = threshold andThen floodFill

        SpotRegionsFn(from, fill(to))

    }
}

Then draw the image as follows:

import java.io.File
import java.awt.{Color, Graphics2D}
import java.awt.geom.{Ellipse2D}
import java.awt.image.BufferedImage
import org.mbari.esp.ia.imglib.ToRGBFn

val points = regions.map(_.centroid)
val circles = points.map(p => new Ellipse2D.Double(p.x - 2, p.y - 2, 4, 4))

val colorImage = ToRGBFn(image).getBufferedImage
val g2 = colorImage.getGraphics.asInstanceOf[Graphics2D]
g2.setPaint(Color.GREEN)
circles.foreach(g2.draw(_))
io.write(colorImage, new File("pcr11jun2520h2000ml40s-with-extracted-fiducials.png"))
g2.dispose()


Document generated by Confluence on Feb 03, 2026 14:16